#include <zenoui/widgets/LabelWidget.hpp>
#include <zenoui/check_gl.hpp>
#include <zenoui/FontData.hpp>
#include <zenoui/Game.hpp>
#include <zenox/unicode.hpp>
#include <zenox/minilog.hpp>
#include <algorithm>

namespace zenoui {

void LabelWidget::paint() {
    EmptyWidget::paint();
    label_paint();
}

ZENO_HOT_FN void LabelWidget::label_paint() {
    if (m_text.empty()) [[unlikely]] return;
    if (m_font_color.a <= 0.0f) return;
    auto lrect = label_rect();
    CHECK_GL(glColor4fv(glm::value_ptr(m_font_color)));
    do_require_text_cache(9);
    auto trect = m_cached_text_rect;
    /* print(trect.m_topleft, trect.m_size); */
    auto ntopleft = lrect.m_topleft - trect.m_topleft;
    m_cached_drawcall(ntopleft.x, ntopleft.y);
#ifndef NDEBUG
    if (std::getenv("ZENO_SHOW_DEBUG_RECT")) [[unlikely]] {
        auto myrect = lrect;
        // print(myrect.topleft(), myrect.size());
        CHECK_GL(glPushAttrib(GL_ALL_ATTRIB_BITS));
        CHECK_GL(glEnable(GL_LINE_STIPPLE));
        CHECK_GL(glLineWidth(2.0f));
        CHECK_GL(glLineStipple(2.0f, 0b0101010101010101));
        glBegin(GL_LINE_STRIP);
        glColor4fv(glm::value_ptr(glm::vec4(0.7f, 0.4f, 0.1f, 0.4f)));
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y);
        glVertex2f(myrect.m_topleft.x + myrect.m_size.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y + myrect.m_size.y);
        glVertex2f(myrect.m_topleft.x, myrect.m_topleft.y);
        CHECK_GL(glEnd());
        CHECK_GL(glPopAttrib());
    }
#endif
}

ZENO_HOT_FN Rect LabelWidget::label_rect() const {
    auto const &mrect = inner_rect();
    do_require_text_cache(1);
    auto textrect = m_cached_text_rect;
    // mock rect according to text alignment property
    /* auto m_alignment=TextAlignLeft | TextAlignTop;// */
    switch (m_alignment & (TextAlignLeft | TextAlignRight)) {
    case TextAlignLeft:
        textrect.m_topleft.x = mrect.topleft().x;
        break;
    case TextAlignRight:
        textrect.m_topleft.x = mrect.bottomright().x - textrect.size().x;
        break;
    default:
        textrect.m_topleft.x = mrect.center().x - textrect.size().x * 0.5f;
        break;
    }
    switch (m_alignment & (TextAlignTop | TextAlignBottom)) {
    case TextAlignTop:
        textrect.m_topleft.y = mrect.topleft().y;
        break;
    case TextAlignBottom:
        textrect.m_topleft.y = mrect.bottomright().y - textrect.size().y;
        break;
    default:
        textrect.m_topleft.y = mrect.center().y - textrect.size().y * 0.5f;
        break;
    }
    return Rect{textrect.m_topleft, textrect.m_size};
}

float LabelWidget::label_query_index(glm::vec2 cpos) const {
    auto lrect = label_rect();
    /* if (!lrect.contains(cpos)) */
    /*     return -1.0f; */
    auto rel_x = cpos.x - lrect.m_topleft.x;
    auto rel_y = cpos.y - lrect.m_topleft.y;
    zenox::log_trace("label_query_index got rel xy {} {}", rel_x, rel_y);
    auto yindex = (int)std::floor(rel_y / m_font_size);
    do_require_text_cache(6);
    auto const &scanxy = m_cached_scanxy;
    yindex = std::max(std::min(yindex, (int)scanxy.size() - 1), 0);
    zenox::log_trace("in label_query_index, yindex is {}", yindex);
    auto const &scanx = scanxy[yindex];
    auto eit = std::lower_bound(scanx.begin(), scanx.end(), rel_x);
    zenox::log_trace("in label_query_index, std::lower_bound gives xindex {}", eit - scanx.begin());
    float val;
    if (eit == scanx.end()) {
        val = (float)(scanx.size() - 1);
    } else if (eit == scanx.begin()) {
        val = 0.0f;
    } else {
        auto bit = std::prev(eit);
        /* print(*eit, *bit); */
        float fac = *eit - *bit ? (rel_x - *bit) / (*eit - *bit) : 0.0f;
        size_t bid = bit - scanx.begin();
        val = std::max(0.0f, std::min((float)bid + fac, (float)(scanx.size() - 1)));
        /* print(bid, fac); */
    }
    zenox::log_trace("in label_query_index, x compnent val is {}", val);
    /* size_t base = 0; */
    /* for (; base < m_text.size() && yindex > 0; base++) { */
    /*     if (m_text[base] == U'\n') { */
    /*         yindex--; */
    /*     } */
    /* } */
    size_t base = yindex > 0 && !m_cached_newlines.empty()
        ? m_cached_newlines[std::min((size_t)yindex - 1, m_cached_newlines.size() - 1)] + 1 : 0;
    zenox::log_trace("in label_query_index, y compnent val is {}", base);
    return base + val;
}

glm::vec2 LabelWidget::label_index_char_rel_xy(size_t index) const {
    zenox::log_trace("label_index_char_rel_xy called with index {}", index);
    do_require_text_cache(6);
    auto const &scanxy = m_cached_scanxy;
    if (scanxy.empty()) [[unlikely]] return glm::vec2(0, 0);
    size_t lineno = std::lower_bound(m_cached_newlines.begin(), m_cached_newlines.end(), index) - m_cached_newlines.begin();
    size_t base = lineno > 0 && !m_cached_newlines.empty()
        ? m_cached_newlines[std::min(lineno - 1, m_cached_newlines.size() - 1)] + 1 : 0;
    auto const &scanx = scanxy[std::min(lineno, scanxy.size() - 1)];
    index -= base;
    zenox::log_trace("label_index_char_rel_xy decomp to xno {} yno {} (base {})", index, lineno, base);
    auto yoff = lineno * m_font_size;
    if (scanx.empty()) [[unlikely]] return glm::vec2(0, yoff);
    if (index >= scanx.size()) [[unlikely]] index = scanx.size() - 1;
    return glm::vec2(scanx[index], yoff);
}


ZENO_HOT_FN void LabelWidget::do_invalidate_text_cache(int mask) {
    m_cache_valid_mask &= ~mask;
}

ZENO_HOT_FN void LabelWidget::do_require_text_cache(int mask) const {
    mask &= ~m_cache_valid_mask;
    if (!mask) [[likely]] return;
    zenox::log_trace("updating label text cache mask={} for UTF-32 string length {}", mask, m_text.size());
    if (!m_cached_font_data || (mask & 16)) {
        m_cached_font_data = Game::instance().get_font_cache()->get_font_by_name(m_font_name, m_font_size);
    }
    if (mask & 1) {
        m_cached_text_rect = m_cached_font_data->predict_size_utf32(m_text, m_font_size);
        /* zenox::log_info("{} {}", m_cached_text_rect.topleft(), m_cached_text_rect.size()); */
    }
    if (mask & 2) {
        m_cached_scanxy = m_cached_font_data->scan_xypos_utf32(m_text, m_font_size);
    }
    if (mask & 4) {
        m_cached_newlines.clear();
        for (size_t i = 0; i < m_text.size(); i++) {
            if (m_text[i] == '\n') {
                m_cached_newlines.push_back(i);
            }
        }
    }
    if (mask & 8) {
        m_cached_drawcall = m_cached_font_data->prepare_draw_utf32(m_text, m_font_size);
    }
    m_cache_valid_mask |= mask;
}

void LabelWidget::property(FontNameProperty *prop) {
    bool is_set = prop->has_value();
    prop->by_ref(m_font_name);
    if (is_set) {
        do_invalidate_text_cache(27);
    }
}

void LabelWidget::property(FontSizeProperty *prop) {
    bool is_set = prop->has_value();
    prop->by_ref(m_font_size);
    if (is_set) {
        do_invalidate_text_cache(11);
    }
}

void LabelWidget::property(FontColorProperty *prop) {
    prop->by_ref(m_font_color);
}

void LabelWidget::property(TextAlignmentProperty *prop) {
    prop->by_ref(m_alignment);
}

void LabelWidget::property(TextProperty *prop) {
    bool is_set = prop->has_value();
    prop->by_ref(m_text);
    if (is_set) {
        do_invalidate_text_cache(15);
    }
}

LabelWidget::LabelWidget() = default;
LabelWidget::~LabelWidget() = default;

}
